Skip to content

An animated solve must fit its budget, and a burst must never be cut in half - #12

Merged
JWriter20 merged 1 commit into
mainfrom
fix/an-animated-solve-must-fit-its-budget
Aug 23, 2026
Merged

An animated solve must fit its budget, and a burst must never be cut in half#12
JWriter20 merged 1 commit into
mainfrom
fix/an-animated-solve-must-fit-its-budget

Conversation

@JWriter20

Copy link
Copy Markdown
Owner

overallSolveTimeoutMs / overall_solve_timeout_ms is 45 s and it is sized for rounds — "a round costs ~4-7 s, so six is the budget", which no-progress.test.ts pins. Recording an animated challenge is not a round. It is a fixed extra stage costing the burst, the slice, one multi-image inference (six keyframes, several times a still's) and the wait for the widget to come back round to the frame the model chose. Nothing in the 45 s was ever set aside for it.

So every escalation to a recording ran the clock out, and reported a timeout — a message about the model being slow, for a budget that had no room for what the solver had just decided to do.

Measured

CaptchaKrakenFinetune Tier 3 run 32596340560, 2026-08-22:

port fixture elapsed reported as
python hcaptcha_fish_swim_different 45.7s, 48.5s, 49.2s exceeded overall_solve_timeout_ms during recording the animated challenge
python hcaptcha_number_with_highest_value_video 49.4s, 50.5s same
python hcaptcha_tile_flip_video 52.6s, 52.1s same
js hcaptcha_click_image_by_traits 52.4s, 49.7s Captcha solve timed out after 45000ms (attempt 6/6)
js hcaptcha_connect_path 50.2s same
js hcaptcha_grid_3x3_property 49.7s, 49.4s same

The same fixtures solve in 11-20 s on the rounds where the still path happens to answer them. It is the escalation that does not fit, not the puzzle.

The escalation buys its own budget, once

video_budget_ms() = burst + keyframe wait + one multi-image inference; 18 s on the defaults, granted the first time a recording starts and never again in the same solve. Derived, so a longer burst carries its own budget rather than quietly reintroducing this. Worst case 63 s, still bounded.

An extension, not a looser default: a solve that never escalates keeps exactly the deadline the caller configured, and video_solve_enabled=False gets no grant at all — that switch already means "fail fast rather than spend the recording time".

Both ports, same arithmetic, because CLAUDE.md 1c — a fixture that passed on one port and timed out on the other reads as a driver bug.

A half-recorded burst is worthless

The python port checked the overall deadline per frame, so a burst starting at 41 s died at frame 27 of 40 — throwing away both the frames and the ~3 s spent making them. The slicer reads a clip's temporal structure; stopping early does not yield a shorter answer, it yields a recording that may not contain the screen the answer is on.

Checked once now, before the first frame, and if the budget cannot fit a whole burst it says so and names the knobs. A separate, looser bound still catches a genuinely hung screenshot and reports it as that.

Tests

9 new. Both regression tests fail on the old behaviour with the exact production message:

FAILED test_a_burst_is_never_abandoned_partway_for_being_over_budget
  'captcha solve exceeded overall_solve_timeout_ms (45000ms) during recording the animated challenge'
FAILED test_recording_extends_the_deadline_once_and_only_once

python/tests 405 passed / 15 skipped, js 85 passed, tsc --noEmit clean.

Pre-existing on main and untouched here: 4 errors in test_browser_compat.py — identical on a clean origin/main, and already fixed on #10's branch.

🤖 Generated with Claude Code

…in half

`overallSolveTimeoutMs` / `overall_solve_timeout_ms` is 45s and it is sized for
ROUNDS — "a round costs ~4-7s, so six is the budget", which no-progress.test.ts
pins. Recording an animated challenge is not a round. It is a fixed extra stage
costing the burst, the slice, one MULTI-IMAGE inference (six keyframes, several
times a still's) and the wait for the widget to come back round to the frame the
model chose. Nothing in the 45s was ever set aside for it.

So every escalation to a recording ran the clock out, and reported a TIMEOUT —
a message about the model being slow, for a budget that had no room for what the
solver had just decided to do.

MEASURED, CaptchaKrakenFinetune Tier 3 run 32596340560 (2026-08-22):

  python  hcaptcha_fish_swim_different            45.7s, 48.5s, 49.2s
          hcaptcha_number_with_highest_value_video 49.4s, 50.5s
          hcaptcha_tile_flip_video                 52.6s, 52.1s
            -> "exceeded overall_solve_timeout_ms during recording the
                animated challenge"
  js      hcaptcha_click_image_by_traits           52.4s, 49.7s
          hcaptcha_connect_path                    50.2s
          hcaptcha_grid_3x3_property               49.7s, 49.4s
            -> "Captcha solve timed out after 45000ms (attempt 6/6)"

The same fixtures solve in 11-20s on the rounds where the still path happens to
answer them. It is the escalation that does not fit, not the puzzle.

## The escalation buys its own budget, once

`video_budget_ms()` = burst + keyframe wait + one multi-image inference; 18s on
the defaults, granted the first time a recording starts and never again in the
same solve. DERIVED, so a longer burst carries its own budget rather than
quietly reintroducing this. Worst case is 63s, still bounded.

An EXTENSION rather than a looser default: a solve that never escalates keeps
exactly the deadline the caller configured, and a caller who set
`video_solve_enabled=False` gets no grant at all — that switch already means
"fail fast rather than spend the recording time".

Both ports, same arithmetic. CLAUDE.md 1c: a fixture that passed on one port and
timed out on the other reads as a driver bug.

## A half-recorded burst is worthless

The python port checked the overall deadline PER FRAME, so a burst that started
at 41s died at frame 27 of 40 — throwing away both the frames and the ~3s spent
making them. The slicer reads a clip's temporal structure; stopping early does
not yield a shorter answer, it yields a recording that may not contain the
screen the answer is on.

Checked ONCE now, before the first frame, and if the budget cannot fit a whole
burst it says so and names the knobs. The burst is fixed-length, so it is not
one of the places that "can legitimately spin for a long time" that
`_check_deadline` exists for; a separate, looser bound catches a genuinely hung
screenshot and reports it as that.

9 new tests. Both regression tests fail on the old behaviour with the exact
production message. Pre-existing on main and untouched here: 4 errors in
test_browser_compat.py (fixed on #10's branch).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JWriter20

Copy link
Copy Markdown
Owner Author

✓ Tier 3 driver-gate — pass

Aggregate: 0.852 · 6/10 families passing · 13 pair(s) not solved

Port Pairs solved
js 0.886
python 0.818
Vendor family Solve rate
botdetect 1.000
geetest 0.786
hcaptcha 0.885
lemin 1.000
mtcaptcha 1.000
prosopo 1.000
recaptcha 0.500
tencent 0.000
yandex 1.000
yidun 1.000

@JWriter20
JWriter20 merged commit d913023 into main Aug 23, 2026
7 checks passed
@JWriter20
JWriter20 deleted the fix/an-animated-solve-must-fit-its-budget branch August 23, 2026 04:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant